﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using Sustainalytics.Entities;
using Sustainalytics.Events.Exceptions;
using Sustainalytics.Events.Service.Errors;
using Sustainalytics.Events.Service.Map;
using Sustainalytics.Events.Service.Models;
using Sustainalytics.Utils;
using Sustainalytics.Claims;
using Sustainalytics.Events.Service.Utility;
using Sustainalytics.Events.BL;
using Sustainalytics.Replication.Publisher;
using MongoDB.Driver;
using System.Web.Http.Cors;

namespace Sustainalytics.Events.Service.Controllers
{
    [EnableCors(origins: "*", headers: "*", methods: "*")]
    [RoutePrefix("api/v1/events")]
    public class EventsController : ApiController
    {
        private readonly IEventsManagement _eventsManager;
        private readonly IClaimsChecker _claimsChecker;


        public EventsController(
            IClaimsChecker claimsChecker,
            ServiceBusPublisher serviceBusPublisher,
            IMongoCollection<Event> eventCollection)
        {
            _claimsChecker = claimsChecker;
            _eventsManager = EventsManagement.GetInstance(eventCollection, serviceBusPublisher);
        }
  
        [Route("")]
        public HttpResponseMessage Get([FromUri]EventsListOptionsDto listOptions, bool fromAdmin = false, bool truncateBody = false)
        {
            try
            {
                if (fromAdmin && !_claimsChecker.ValidateAdminClaim(this))
                    throw new AdminClaimNullException();

                if (!fromAdmin && !_claimsChecker.ValidateClaim(this))
                    throw new DashboardClaimNullException();

                listOptions = listOptions ?? EventsListOptionsDto.Default;
                var filter = GetEventFilterFromListOptions(listOptions);

                var validationErrors = Validate(listOptions);
                if (validationErrors != null)
                    return Request.CreateResponse(HttpStatusCode.BadRequest, validationErrors);

                if (listOptions.GetCount)
                {
                    var count = _eventsManager.GetEventsCount(filter);

                    return Request.CreateResponse(HttpStatusCode.OK, new EventsCountDto { Count = count });
                }

                List<Event> results = null;
                try
                {
                    results = _eventsManager.GetEvents(filter, listOptions.Skip, listOptions.Take, listOptions.OrderBy, listOptions.OrderAscending);
                }
                catch (InvalidPropertyNameException exception)
                {
                    var mapper = EventListOptionsValidationMapper.Instance;
                    var errorDetails = mapper.Map(exception);

                    return Request.CreateResponse(HttpStatusCode.BadRequest, errorDetails);
                }

                List<EventDto> eventsDtos = null;
                if (results != null)
                {
                    var map = new EventMapper();
                    bool includeAllFields = listOptions.IncludeAllFields;
                    eventsDtos = results.Select(e => map.Map(e, includeAllFields, truncateBody)).ToList();
                }

                return Request.CreateResponse(HttpStatusCode.OK, new GetEventsDto { Items = eventsDtos });
            }
            catch (DashboardClaimNullException ex)
            {
                return Request.CreateResponse(HttpStatusCode.Forbidden, ex.Message);
            }
            catch (AdminClaimNullException)
            {
                return Request.CreateResponse(HttpStatusCode.Forbidden, new { ErrorCode = 3003 });
            }
        }

      
        //used only from Admin
        [HttpGet]
        [Route("{eventId}")]
        public HttpResponseMessage Get(Guid eventId)
        {
            try
            {
                if (!_claimsChecker.ValidateAdminClaim(this) && !_claimsChecker.ValidateClaim(this))
                    throw new AdminClaimNullException();

                Event eventObj = _eventsManager.GetEvent(eventId);

                if (eventObj == null)
                    return Request.CreateErrorResponse(HttpStatusCode.NotFound, "The requested event was not found!");

                return Request.CreateResponse(HttpStatusCode.OK, EventMapper.Instance.Map(eventObj, true));
            }
            catch (AdminClaimNullException)
            {
                return Request.CreateResponse(HttpStatusCode.Forbidden, new { ErrorCode = 3003 });
            }
        }

        /// <summary>
        /// Add event.
        /// </summary>
        /// <param name="createEventDto"> createEventDto </param>
        /// <returns type="Sustainalytics.Events.Service.Models.EventDto">Created Event</returns>
        [Route("")]
        public HttpResponseMessage Post(CreateEventDto createEventDto)
        {
            var mapper = EventMapper.Instance;
            var eventValidationMapper = EventValidationMapper.Instance;

            try
            {
                if (!_claimsChecker.ValidateAdminClaim(this))
                    throw new AdminClaimNullException();
                
                var eventObj = mapper.Map(createEventDto);

                eventObj = this._eventsManager.AddEvent(eventObj);

                return this.Request.CreateResponse(HttpStatusCode.OK, mapper.Map(eventObj, true));
            }
            catch (AdminClaimNullException)
            {
                return this.Request.CreateResponse(HttpStatusCode.Forbidden, new { ErrorCode = 3003 });
            }
            catch (EventValidationException validationException)
            {
                return this.Request.CreateResponse(HttpStatusCode.BadRequest, eventValidationMapper.Map(validationException));
            }
        }

        /// <summary>
        /// Add event.
        /// </summary>
        /// <param name="createEventDto"> createEventDto </param>
        /// <returns type="Sustainalytics.Events.Service.Models.EventDto">Created Event</returns>
        [Route("")]
        public HttpResponseMessage Put(EventUpdateDto eventUpdateDto)
        {
            var eventMapper = EventMapper.Instance;
            var eventValidationMapper = EventValidationMapper.Instance;

            try
            {
                if (!_claimsChecker.ValidateAdminClaim(this))
                    throw new AdminClaimNullException();

                var eventUpdate = eventMapper.Map(eventUpdateDto);

                var eventObj = this._eventsManager.Update(eventUpdateDto.Id, eventUpdate);

                return this.Request.CreateResponse(HttpStatusCode.OK, eventMapper.Map(eventObj, true));
            }
            catch (AdminClaimNullException)
            {
                return this.Request.CreateResponse(HttpStatusCode.Forbidden, new { ErrorCode = 3003 });
            }
            catch (UpdatePublishedEventException invalidStateException)
            {
                return this.Request.CreateResponse(HttpStatusCode.BadRequest, eventMapper.Map(invalidStateException));
            }
            catch (EventNotFoundException eventNotFound)
            {
                return this.Request.CreateResponse(HttpStatusCode.NotImplemented, eventMapper.Map(eventNotFound));
            }
            catch (EventValidationException validationException)
            {
                return this.Request.CreateResponse(HttpStatusCode.BadRequest, eventValidationMapper.Map(validationException));
            }
            
        }

        [Route("")]
        public HttpResponseMessage Patch([FromBody]EventsPatch patchData)
        {
            try
            {
                if (!_claimsChecker.ValidateAdminClaim(this))
                    throw new AdminClaimNullException();

                if(patchData != null && patchData.PublishItems != null)
                    this._eventsManager.Publish(patchData.PublishItems);

                if (patchData != null && patchData.DeleteItems != null)
                    this._eventsManager.Delete(patchData.DeleteItems);

                return this.Request.CreateResponse(HttpStatusCode.OK);
            }
            catch (AdminClaimNullException)
            {
                return this.Request.CreateResponse(HttpStatusCode.Forbidden, new { ErrorCode = 3003 });
            }
        }

        #region validation
        protected ValidationErrorDto Validate(EventsListOptionsDto options)
        {
            if (options == null)
                return null;

            var validationErrors = new ValidationErrorDto((int)EventsErrorCodes.EventsListOptions_FailedValidation);

            if (options.Skip < 0)
            {
                validationErrors.Add(new ValueOutOfRangeDto<int> { Value = options.Skip, Min = 0, Code = (int)EventsErrorCodes.EventsListOptions_InvalidSkip });
            }

            if (options.Take < 0)
            {
                validationErrors.Add(new ValueOutOfRangeDto<int> { Value = options.Take, Min = 0, Code = (int)EventsErrorCodes.EventsListOptions_InvalidTake });
            }


            if (validationErrors.Details.Count > 0)
            {
                return validationErrors;
            }

            return null;
        }
        #endregion

        private static EventsFilter GetEventFilterFromListOptions(EventsListOptionsDto listOptions)
        {
            var filter = new EventsFilter
            {
                SearchText = listOptions.Term,
                Sponsorship = null,
                IsPublished = listOptions.IsPublished ?? false,

                StartDateBegin = (string.IsNullOrEmpty(listOptions.StartDateBegin) ? (DateTime?)null : Convert.ToDateTime(listOptions.StartDateBegin)),
                StartDateEnd = (string.IsNullOrEmpty(listOptions.StartDateEnd) ? (DateTime?)null : Convert.ToDateTime(listOptions.StartDateEnd)),
            };

            if (listOptions.EventType.HasValue)
                filter.Sponsorship = listOptions.EventType == EventTypeEnum.Sponsored;
            return filter;
        }

    }
}
